{
  "metadata": {
    "kernelspec": {
      "name": "python",
      "display_name": "Python (Pyodide)",
      "language": "python"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "python",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.8"
    }
  },
  "nbformat_minor": 5,
  "nbformat": 4,
  "cells": [
    {
      "id": "b4da38ad",
      "cell_type": "markdown",
      "source": "# 0. Introduction\nWelcome to the Risk-Parity Portfolio Analysis project! In this project, you'll explore the concept of risk-parity, a popular portfolio management strategy that aims to allocate investments in a way that equalizes the risk contribution of each asset. This approach is widely used by hedge funds and institutional investors to create more balanced portfolios that are less sensitive to market volatility.\n\nThroughout this project, you'll learn how to download financial data for various assets, calculate returns, and compute risk-parity weights using Python. By the end, you'll evaluate the performance of your risk-parity portfolio through key financial metrics such as annualized return, volatility, and Sharpe ratio. This hands-on experience will deepen your understanding of portfolio management and give you practical skills in financial data analysis. Whether you're new to finance or looking to enhance your quantitative finance skills, this project provides a solid foundation in risk-parity strategies and their application in real-world scenarios.\n\n## Getting Started: Tips and Instructions\n\n1. **Familiarize Yourself with the Notebook Structure:**\n   - The notebook is organized into clearly defined sections, each focusing on a specific aspect of the risk-parity portfolio analysis. Take a moment to glance through the sections to understand the overall workflow.\n\n2. **Review the Data:**\n   - Since the data is already available, start by exploring the initial few cells to understand the data structure and what each column represents. This will give you context for the calculations and analysis you'll be performing.\n\n3. **Run Cells Sequentially:**\n   - Work through the notebook by executing each code cell in order. This will help you build your analysis step by step and ensure that each part of the project is functioning as expected.\n\n4. **Experiment with Parameters:**\n   - Feel free to tweak parameters such as the rolling window size for calculating risk-parity weights or adjust the assets included in the portfolio. Experimenting will help deepen your understanding of how these factors influence portfolio performance.\n\n5. **Leverage the Plots:**\n   - Use the generated plots to visually assess the impact of your calculations. The visualizations are crucial for understanding the risk-parity approach and how different assets contribute to the portfolio.\n\n6. **Reflect:**\n   - As you work through the notebook, consider why each step is necessary and how it contributes to the overall goal of building a risk-parity portfolio. If something isn’t clear, take a moment to reflect or explore additional resources.\n\nEnjoy the process of exploring risk-parity strategies, and don't hesitate to dive deeper into the data or code to enhance your learning experience!",
      "metadata": {
        "jp-MarkdownHeadingCollapsed": true
      }
    },
    {
      "id": "c6dc699e",
      "cell_type": "markdown",
      "source": "# 1. Import Libraries",
      "metadata": {}
    },
    {
      "id": "1aa05430",
      "cell_type": "code",
      "source": "# Load necessary libraries\nimport pandas as pd\nimport numpy as np\nimport yfinance as yf\nimport matplotlib.pyplot as plt\nimport matplotlib.dates as mdates\nimport matplotlib.ticker as ticker",
      "metadata": {},
      "outputs": [],
      "execution_count": 2
    },
    {
      "id": "8c4a4a35",
      "cell_type": "markdown",
      "source": "# 2. Download Financial Data\nWe will download front-month futures data for S&P500, 10-year Treasuries, gold, and US dollar using the `yfinance` library.",
      "metadata": {}
    },
    {
      "id": "fa831031",
      "cell_type": "code",
      "source": "# Download front-month futures data\nsymbols = ['ES=F', 'ZN=F', 'GC=F', 'DX=F']\ndata = yf.download(symbols)",
      "metadata": {},
      "outputs": [],
      "execution_count": null
    },
    {
      "id": "189bc851",
      "cell_type": "markdown",
      "source": "### 3. Resample Data\n\nIn this section, we aim to reduce the noise in the daily financial data by resampling it to a monthly frequency. Resampling is a common technique in time series analysis, allowing us to aggregate data points over a specified time period. This helps in smoothing out short-term fluctuations and making the data more manageable for analysis.\n",
      "metadata": {}
    },
    {
      "id": "aedbfa86",
      "cell_type": "code",
      "source": "# Resample data to monthly frequency\ndata = None # YOUR CODE HERE\n# Convert index to datetime\ndata.index = None # YOUR CODE HERE",
      "metadata": {},
      "outputs": [],
      "execution_count": 4
    },
    {
      "id": "c114be39",
      "cell_type": "code",
      "source": "print(type(data.index))",
      "metadata": {},
      "outputs": [],
      "execution_count": null
    },
    {
      "id": "052a4fab-aa9d-4945-80bb-876bd2418f9f",
      "cell_type": "markdown",
      "source": "### 4. Clean and Prepare Data\n\nIn this step, we will focus on extracting the relevant data, handling missing values, and ensuring the data is ready for analysis. Specifically, we'll subset the adjusted close prices from our dataset, fill any missing values, and drop rows with unknown prices.\n\n#### Steps to Clean and Prepare Data\n\n1. **Subset Adjusted Close Prices**     \n2. **Fill Missing Values (NaNs)**\n3. **Drop Rows with Remaining NaNs**\n4. **Ensure Correct Date Formatting**\n\nBy following these steps, we will have a clean dataset of adjusted close prices that is free of missing values and properly formatted for time series analysis.\n",
      "metadata": {}
    },
    {
      "id": "b0ebfafd",
      "cell_type": "code",
      "source": "# Subset adjusted close prices and fill NaNs\nprices = None # YOUR CODE HERE\n# Convert index to datetime\nprices.index = None # YOUR CODE HERE",
      "metadata": {},
      "outputs": [],
      "execution_count": 6
    },
    {
      "id": "07f745df",
      "cell_type": "code",
      "source": "print(prices)",
      "metadata": {},
      "outputs": [],
      "execution_count": null
    },
    {
      "id": "a74a8a08",
      "cell_type": "code",
      "source": "prices.plot()\nplt.show()",
      "metadata": {},
      "outputs": [],
      "execution_count": null
    },
    {
      "id": "63d62f03",
      "cell_type": "code",
      "source": "prices.plot()\nplt.yscale(\"log\")\nplt.show()",
      "metadata": {},
      "outputs": [],
      "execution_count": null
    },
    {
      "id": "b2f718a0",
      "cell_type": "markdown",
      "source": "### 5. Calculate Returns\n\nIn financial analysis, one of the key metrics is the return on an investment, which shows how much the price of an asset has increased or decreased over a certain period. We will calculate the **arithmetic returns** of the adjusted close prices to measure this change over time.\n\n#### Steps to Calculate Arithmetic Returns\n\n1. **Understanding Arithmetic Returns:**\n   - Arithmetic returns measure the percentage change in the price of an asset from one period to the next. \n   - The formula for calculating the arithmetic return for a given period is:\n\n     $\n     R_t = \\frac{P_t - P_{t-1}}{P_{t-1}}\n     $\n     \n     where:\n     - $ R_t $ is the return at time $ t $,\n     - $ P_t $ is the price at time $ t $,\n     - $ P_{t-1} $ is the price at the previous time period.\n\n   - This formula can be simplified to:\n     $\n     R_t = \\frac{P_t}{P_{t-1}} - 1\n     $\n   - However, in practice, it is common to use the percentage change function provided by Pandas, which handles this calculation efficiently.\n\n2. **Calculate Percentage Change**\n3. **Handle Missing Values:**\n\nBy following these steps, we will have a series of arithmetic returns that represent the day-to-day percentage changes in the asset's price, which can be used for further analysis such as calculating cumulative returns, volatility, or risk metrics.\n",
      "metadata": {}
    },
    {
      "id": "85eb7ff4",
      "cell_type": "code",
      "source": "# Compute arithmetic returns\nreturns = None # YOUR CODE HERE",
      "metadata": {},
      "outputs": [],
      "execution_count": 10
    },
    {
      "id": "a56b10be",
      "cell_type": "code",
      "source": "print(returns)",
      "metadata": {},
      "outputs": [],
      "execution_count": null
    },
    {
      "id": "60e8526a",
      "cell_type": "markdown",
      "source": "### 6. Compute Risk-Parity Weights\n\nRisk-parity is an investment strategy that seeks to allocate portfolio weights in a way that each asset contributes equally to the overall portfolio risk. This method is especially useful in diversifying risk across different assets with varying levels of volatility.\n\n#### Steps to Compute Risk-Parity Weights\n\n1. **Understanding Risk-Parity:**\n   - The idea behind risk-parity is to allocate more weight to less volatile assets and less weight to more volatile assets, thereby equalizing the risk contribution of each asset in the portfolio.\n   - The weight of each asset in the portfolio is inversely proportional to its volatility.\n\n2. **Calculate Rolling Volatility:**\n   - Volatility is a statistical measure of the dispersion of returns for a given security or market index.\n     \n3. **Compute Inverse Volatility:**\n4. **Normalize Weights:**\n   - Once we have the inverse volatilities, we normalize them so that the sum of the weights for each time period equals 1. This ensures that we have a proper weight distribution across assets.\n   - We achieve this by dividing the inverse volatility of each asset by the sum of the inverse volatilities across all assets for each time period.\n     \n5. **Shift Weights for Practical Application:**\n   - In a real-world scenario, we can only make investment decisions based on information that is available at the time of making the decision. Therefore, we shift the computed weights by one period to ensure that the weights at time $ t $ are based on data up to $ t-1 $.      - This shift ensures that the weights applied are based on historical data up to the previous period, which is crucial for avoiding look-ahead bias.\n\nBy following these steps, we generate a time series of risk-parity weights that can be used to allocate assets in a way that balances the risk across the portfolio. The resulting weights adapt dynamically based on the rolling volatility, ensuring that each asset contributes equally to the overall risk over time.\n",
      "metadata": {}
    },
    {
      "id": "d7557879",
      "cell_type": "code",
      "source": "def compute_risk_parity_weights(returns, window_size=36):\n    # Compute volatility known at time t\n    rolling_vol = None # YOUR CODE HERE\n    rolling_inverse_vol = None # YOUR CODE HERE\n    # Divide inverse volatility by the sum of inverse volatilities\n    risk_parity_weights = None # YOUR CODE HERE\n    # Shift weights by one period to use only information available at time t\n    risk_parity_weights = None # YOUR CODE HERE\n    return risk_parity_weights\n\nrisk_parity_weights = compute_risk_parity_weights(returns, 36)",
      "metadata": {},
      "outputs": [],
      "execution_count": 12
    },
    {
      "id": "557075b0",
      "cell_type": "code",
      "source": "risk_parity_weights.plot()\nplt.show()",
      "metadata": {},
      "outputs": [],
      "execution_count": null
    },
    {
      "id": "714c77a8",
      "cell_type": "markdown",
      "source": "### 7. Calculate Weighted Returns\n\nOnce we have determined the risk-parity weights, the next step is to calculate the weighted returns for each asset and then aggregate them to obtain the portfolio returns. This process allows us to understand how the risk-parity allocation strategy would have performed over time.\n\n#### Steps to Calculate Weighted Returns\n\n1. **Understanding Weighted Returns:**\n   - The weighted return of an asset is calculated by multiplying its individual return by the corresponding weight in the portfolio. \n   - The total portfolio return at any given time is the sum of the weighted returns of all assets.\n\n2. **Apply Weights to Returns:**\n   - We will multiply the returns of each asset by its corresponding risk-parity weight for each time period. This will give us the weighted return for each asset.\n3. **Handle Missing Data:**\n   - We drop any rows with missing values (`NaN`) that may result from the multiplication process. This ensures that we only consider time periods with complete data.\n4. **Aggregate Weighted Returns:**\n   - To calculate the portfolio's return at each time period, sum the weighted returns across all assets. This gives the overall return of the portfolio for each time period.\n\n#### Mathematical Representation:\n\nGiven the returns $ r_{i,t} $ for asset $ i $ at time $ t $, and the risk-parity weight $ w_{i,t} $ for asset $ i $ at time $ t $, the weighted return for asset $ i $ at time $ t $ is:\n\n$ r_{i,t}^{weighted} = r_{i,t} \\times w_{i,t} $\n\nThe total portfolio return at time $ t $ is the sum of the weighted returns for all assets:\n\n$\nR_{portfolio,t} = \\sum_{i=1}^{n} r_{i,t}^{weighted}\n$\n\nWhere $ n $ is the number of assets in the portfolio.\n\nBy following these steps, we obtain the portfolio returns that reflect the performance of the risk-parity strategy over time. This provides insights into how well the strategy balanced risk across different market conditions.\n",
      "metadata": {}
    },
    {
      "id": "abd06b6d",
      "cell_type": "code",
      "source": "# Calculate weighted returns\nweighted_returns = None # YOUR CODE HERE\nrisk_parity_portfolio_returns = None # YOUR CODE HERE",
      "metadata": {},
      "outputs": [],
      "execution_count": 14
    },
    {
      "id": "0210dda0",
      "cell_type": "code",
      "source": "print(risk_parity_portfolio_returns)",
      "metadata": {},
      "outputs": [],
      "execution_count": null
    },
    {
      "id": "bfee09a6",
      "cell_type": "markdown",
      "source": "### 8. Evaluate Portfolio Performance\n\nTo assess the performance of the risk-parity portfolio, we will compute several key financial metrics. These metrics will help us understand the portfolio's return, risk, and overall performance characteristics. Below are the details of the metrics we will calculate, along with the functions used and their corresponding arguments.\n\n1. **Annualized Mean Return**\n   - **Description:** The average return of the portfolio on an annual basis. This is useful for understanding the long-term growth rate of the portfolio.\n2. **Annualized Volatility**\n   - **Description:** A measure of the portfolio's return volatility on an annual basis, indicating the degree of variation in returns.\n   - **Formula:** \n     $\n     \\text{Annualized Volatility} = \\text{Standard Deviation of Monthly Returns} \\times \\sqrt{12}\n     $\n3. **Skewness**\n   - **Description:** Skewness measures the asymmetry of the return distribution. Positive skewness indicates a distribution with a longer right tail, while negative skewness indicates a longer left tail.\n4. **Kurtosis**\n   - **Description:** Kurtosis measures the \"tailedness\" of the return distribution. High kurtosis indicates a distribution with heavy tails and a sharp peak.\n5. **Maximum Drawdown**\n   - **Description:** The maximum observed loss from a peak to a trough of the portfolio's cumulative returns, before a new peak is attained.\n   - **Formula:** \n     $\n     \\text{Drawdown} = \\frac{\\text{Cumulative Return} - \\text{Running Maximum}}{\\text{Running Maximum}}\n     $\n6. **Sharpe Ratio**\n   - **Description:** The Sharpe Ratio measures the risk-adjusted return of the portfolio, calculated as the ratio of the portfolio's excess return (over the risk-free rate, typically assumed to be 0 in this case) to its volatility.\n   - **Formula:** \n     $\n     \\text{Sharpe Ratio} = \\frac{\\text{Annualized Mean Return}}{\\text{Annualized Volatility}}\n     $\n7. **Sortino Ratio**\n   - **Description:** The Sortino Ratio is a variation of the Sharpe Ratio that penalizes only downside volatility, thus providing a better measure of risk-adjusted return for portfolios that have asymmetric return distributions.\n   - **Formula:** \n     $\n     \\text{Sortino Ratio} = \\frac{\\text{Annualized Mean Return}}{\\text{Downside Volatility}}\n     $\n8. **Calmar Ratio**\n   - **Description:** The Calmar Ratio measures the risk-adjusted return of a portfolio by comparing the annualized return to the maximum drawdown, providing insight into performance relative to the worst-case scenario.\n   - **Formula:** \n     $\n     \\text{Calmar Ratio} = \\frac{\\text{Annualized Mean Return}}{-\\text{Maximum Drawdown}}\n     $\n\nAfter calculating these metrics, we will display the results to evaluate the performance of the risk-parity portfolio comprehensively.\n",
      "metadata": {}
    },
    {
      "id": "c1489f4c",
      "cell_type": "code",
      "source": "# Evaluate portfolio performance\nannual_mean_return = None # YOUR CODE HERE\nannual_volatility = None # YOUR CODE HERE\nskewness = None # YOUR CODE HERE\nkurtosis = None # YOUR CODE HERE\n\n# Compute drawdown\ncumulative_returns = None # YOUR CODE HERE\nrunning_max = None # YOUR CODE HERE\ndrawdown = None # YOUR CODE HERE\nmax_drawdown = None # YOUR CODE HERE\n\n# Compute Sharpe ratio\nsharpe_ratio = None # YOUR CODE HERE\n\n# Compute Sortino ratio\ndownside_std = None # YOUR CODE HERE\nsortino_ratio = None # YOUR CODE HERE\n\n# Compute Calmar ratio\ncalmar_ratio = None # YOUR CODE HERE\n\n# Display results\nprint(f\"Mean Annual Return: {annual_mean_return:.4f}\")\nprint(f\"Annual Volatility: {annual_volatility:.4f}\")\nprint(f\"Skewness: {skewness:.4f}\")\nprint(f\"Kurtosis: {kurtosis:.4f}\")\nprint(f\"Maximum Drawdown: {max_drawdown:.4f}\")\nprint(f\"Sharpe Ratio: {sharpe_ratio:.4f}\")\nprint(f\"Sortino Ratio: {sortino_ratio:.4f}\")\nprint(f\"Calmar Ratio: {calmar_ratio:.4f}\")",
      "metadata": {},
      "outputs": [],
      "execution_count": null
    },
    {
      "id": "e2de8713",
      "cell_type": "markdown",
      "source": "### 9. Plot Results\n\nVisualizing the performance of the risk-parity portfolio is crucial for understanding the dynamics of cumulative returns and drawdowns over time. We will create a plot that displays both the cumulative returns and the drawdowns on the same graph. This will allow us to see how the portfolio grows over time and the extent of losses from peak to trough.\n\n#### Steps to Plot the Results:\n\n1. **Initialize the Plot**:\n   - We use `plt.subplots()` to create a figure and an axis object, allowing us to customize the plot.\n   - The `figsize` argument specifies the size of the plot. In this case, we choose a wide format (`14x7`) to better display the time series data.\n   - Example:\n     ```python\n     fig, ax = plt.subplots(figsize=(14, 7))\n     ```\n\n2. **Plot Cumulative Returns**:\n   - The `plot()` function is used to plot the cumulative returns on the axis `ax`.\n   - The `label` argument is used to create a legend entry, and `color` specifies the color of the line.\n   - Example:\n     ```python\n     cumulative_returns.plot(ax=ax, label='Cumulative Returns', color='blue')\n     ```\n\n3. **Plot Drawdown**:\n   - Similarly, the `plot()` function is used to plot the drawdown on the same axis `ax`.\n   - The `label` and `color` arguments distinguish this line from the cumulative returns.\n   - Example:\n     ```python\n     drawdown.plot(ax=ax, label='Drawdown', color='red')\n     ```\n\n4. **Customize the Plot**:\n   - The `set_title()` function sets the title of the plot, making it clear what the graph represents.\n   - The `set_ylabel()` and `set_xlabel()` functions label the y-axis and x-axis, respectively, to indicate what the axes represent (e.g., 'Cumulative Returns' and 'Date').\n   - Example:\n     ```python\n     ax.set_title('Cumulative Returns and Drawdown')\n     ax.set_ylabel('Cumulative Returns')\n     ax.set_xlabel('Date')\n     ```\n\n5. **Add a Legend**:\n   - The `legend()` function adds a legend to the plot, helping to identify which line represents cumulative returns and which represents drawdown.\n   - Example:\n     ```python\n     ax.legend()\n     ```\n\n6. **Display the Plot**:\n   - Finally, `plt.show()` is called to display the plot.\n   - Example:\n     ```python\n     plt.show()\n     ```\n\nThe resulting plot will provide a clear visual representation of how the portfolio's value has evolved over time and the magnitude of any losses (drawdowns) experienced during the period. This visualization is essential for analyzing the risk and return profile of the portfolio.\n",
      "metadata": {}
    },
    {
      "id": "be6313b8",
      "cell_type": "code",
      "source": "# Plot portfolio performance\nfig, ax = plt.subplots(figsize=(14, 7))\n(cumulative_returns - 1).plot(ax=ax, label='Cumulative Returns', color='blue')\ndrawdown.plot(ax=ax, label='Drawdown', color='red')\nax.set_title('Cumulative Returns and Drawdown')\nax.set_ylabel('Cumulative Returns')\nax.set_xlabel('Date')\nax.legend()\nplt.show()",
      "metadata": {},
      "outputs": [],
      "execution_count": null
    }
  ]
}
